/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.amqp.rabbit.stocks.context;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanDefinitionVisitor;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.expression.BeanFactoryAccessor;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
/**
* <p>A Scope implementation that allows for beans to be refreshed dynamically at runtime (see {@link #refresh(String)}
* and {@link #refreshAll()}). If a bean is refreshed then the next time the bean is accessed (i.e. a method is
* executed) a new instance is created. All lifecycle methods are applied to the bean instances, so any destruction
* callbacks that were registered in the bean factory are called when it is refreshed, and then the initialization
* callbacks are invoked as normal when the new instance is created. A new bean instance is created from the original
* bean definition, so any externalized content (property placeholders or expressions in string literals) is
* re-evaluated when it is created.</p>
*
* <p>Note that all beans in this scope are <em>only</em> initialized when first accessed, so the scope forces lazy
* initialization semantics. The implementation involves creating a proxy for every bean in the scope, so there is a
* flag {@link #setProxyTargetClass(boolean) proxyTargetClass} which controls the proxy creation, defaulting to JDK
* dynamic proxies and therefore only exposing the interfaces implemented by a bean. If callers need access to other
* methods then the flag needs to be set (and CGLib present on the classpath). Because this scope automatically proxies
* all its beans, there is no need to add <code><aop:auto-proxy/></code> to any bean definitions.</p>
*
* <p>The scoped proxy approach adopted here has a side benefit that bean instances are automatically
* {@link Serializable}, and can be sent across the wire as long as the receiver has an identical application context on
* the other side. To ensure that the two contexts agree that they are identical they have to have the same
* serialization id. One will be generated automatically by default from the bean names, so two contexts with the same
* bean names are by default able to exchange beans by name. If you need to override the default id then provide an
* explicit {@link #setId(String) id} when the Scope is declared.</p>
*
* @author Dave Syer
*
* @since 3.1
*
*/
@ManagedResource
public class RefreshScope implements Scope, BeanFactoryPostProcessor, DisposableBean {
private static final Log logger = LogFactory.getLog(RefreshScope.class);
private ConcurrentMap<String, BeanCallbackWrapper> cache = new ConcurrentHashMap<String, BeanCallbackWrapper>();
private String name = "refresh";
private boolean proxyTargetClass = false;
private ConfigurableListableBeanFactory beanFactory;
private StandardEvaluationContext evaluationContext;
private String id;
/**
* Manual override for the serialization id that will be used to identify the bean factory. The default is a unique
* key based on the bean names in the bean factory.
*
* @param id the id to set
*/
public void setId(String id) {
this.id = id;
}
/**
* The name of this scope. Default "refresh".
*
* @param name the name value to set
*/
public void setName(String name) {
this.name = name;
}
/**
* Flag to indicate that proxies should be created for the concrete type, not just the interfaces, of the scoped
* beans.
*
* @param proxyTargetClass the flag value to set
*/
public void setProxyTargetClass(boolean proxyTargetClass) {
this.proxyTargetClass = proxyTargetClass;
}
public void destroy() throws Exception {
refreshAll();
}
public Object get(String name, ObjectFactory<?> objectFactory) {
BeanCallbackWrapper value = new BeanCallbackWrapper(name, objectFactory, proxyTargetClass);
BeanCallbackWrapper result = cache.putIfAbsent(name, value);
value = result == null ? value : result;
return value.getBean();
}
public String getConversationId() {
return name;
}
public void registerDestructionCallback(String name, Runnable callback) {
BeanCallbackWrapper value = cache.get(name);
if (value == null) {
return;
}
value.setCallback(callback);
}
public Object remove(String name) {
BeanCallbackWrapper value = cache.get(name);
if (value == null) {
return null;
}
return cache.remove(name, value);
}
public Object resolveContextualObject(String key) {
Expression expression = parseExpression(key);
return expression.getValue(evaluationContext, beanFactory);
}
private Expression parseExpression(String input) {
if (StringUtils.hasText(input)) {
ExpressionParser parser = new SpelExpressionParser();
try {
return parser.parseExpression(input);
} catch (ParseException e) {
throw new IllegalArgumentException("Cannot parse expression: " + input, e);
}
} else {
return null;
}
}
@ManagedOperation(description = "Dispose of the current instance of bean name provided and force a refresh on next method execution.")
public void refresh(String name) {
if (!name.startsWith("scopedTarget.")) {
name = "scopedTarget."+name;
}
BeanCallbackWrapper wrapper = cache.remove(name);
if (wrapper != null) {
wrapper.destroy();
}
}
@ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.")
public void refreshAll() {
List<Throwable> errors = new ArrayList<Throwable>();
Collection<BeanCallbackWrapper> wrappers = new HashSet<BeanCallbackWrapper>(cache.values());
cache.clear();
for (BeanCallbackWrapper wrapper : wrappers) {
try {
wrapper.destroy();
} catch (RuntimeException e) {
errors.add(e);
}
}
if (!errors.isEmpty()) {
throw wrapIfNecessary(errors.get(0));
}
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope(name, this);
setSerializationId(beanFactory);
this.beanFactory = beanFactory;
evaluationContext = new StandardEvaluationContext();
evaluationContext.addPropertyAccessor(new BeanFactoryAccessor());
Assert.state(beanFactory instanceof BeanDefinitionRegistry,
"BeanFactory was not a BeanDefinitionRegistry, so RefreshScope cannot be used.");
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
// Replace this or any of its inner beans with scoped proxy if it
// has this scope
boolean scoped = name.equals(definition.getScope());
Scopifier scopifier = new Scopifier(registry, name, proxyTargetClass, scoped);
scopifier.visitBeanDefinition(definition);
if (scoped) {
createScopedProxy(beanName, definition, registry, proxyTargetClass);
}
}
}
/**
* If the bean factory is a DefaultListableBeanFactory then it can serialize scoped beans and deserialize them in
* another context (even in another JVM), as long as the ids of the bean factories match. This method sets up the
* serialization id to be either the id provided to the scope instance, or if that is null, a hash of all the bean
* names.
*
* @param beanFactory the bean factory to configure
*/
private void setSerializationId(ConfigurableListableBeanFactory beanFactory) {
if (beanFactory instanceof DefaultListableBeanFactory) {
String id = this.id;
if (id == null) {
String names = Arrays.asList(beanFactory.getBeanDefinitionNames()).toString();
logger.debug("Generating bean factory id from names: " + names);
id = UUID.nameUUIDFromBytes(names.getBytes()).toString();
}
logger.info("BeanFactory id=" + id);
((DefaultListableBeanFactory) beanFactory).setSerializationId(id);
} else {
logger.warn("BeanFactory was not a DefaultListableBeanFactory, so RefreshScope beans "
+ "cannot be serialized reliably and passed to a remote JVM.");
}
}
private static RuntimeException wrapIfNecessary(Throwable throwable) {
if (throwable instanceof RuntimeException) {
return (RuntimeException) throwable;
}
if (throwable instanceof Error) {
throw (Error) throwable;
}
return new IllegalStateException(throwable);
}
private static BeanDefinitionHolder createScopedProxy(String beanName, BeanDefinition definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(definition,
beanName), registry, proxyTargetClass);
registry.registerBeanDefinition(beanName, proxyHolder.getBeanDefinition());
return proxyHolder;
}
/**
* Helper class to scan a bean definition hierarchy and force the use of auto-proxy for scoped beans.
*
* @author Dave Syer
*
*/
private static class Scopifier extends BeanDefinitionVisitor {
private final boolean proxyTargetClass;
private final BeanDefinitionRegistry registry;
private final String scope;
private final boolean scoped;
public Scopifier(BeanDefinitionRegistry registry, String scope, boolean proxyTargetClass, boolean scoped) {
super(new StringValueResolver() {
public String resolveStringValue(String value) {
return value;
}
});
this.registry = registry;
this.proxyTargetClass = proxyTargetClass;
this.scope = scope;
this.scoped = scoped;
}
@Override
protected Object resolveValue(Object value) {
BeanDefinition definition = null;
String beanName = null;
if (value instanceof BeanDefinition) {
definition = (BeanDefinition) value;
beanName = BeanDefinitionReaderUtils.generateBeanName(definition, registry);
} else if (value instanceof BeanDefinitionHolder) {
BeanDefinitionHolder holder = (BeanDefinitionHolder) value;
definition = holder.getBeanDefinition();
beanName = holder.getBeanName();
}
if (definition != null) {
boolean nestedScoped = scope.equals(definition.getScope());
boolean scopeChangeRequiresProxy = !scoped && nestedScoped;
if (scopeChangeRequiresProxy) {
// Exit here so that nested inner bean definitions are not
// analysed
return createScopedProxy(beanName, definition, registry, proxyTargetClass);
}
}
// Nested inner bean definitions are recursively analysed here
value = super.resolveValue(value);
return value;
}
}
/**
* Wrapper for a bean instance and any destruction callback (DisposableBean etc.) that is registered for it. If the
* bean is disposable, the wrapper also guards access to the bean: a read lock (allowing concurrent access) is taken
* for all method executions except the destruction callback, which uses a write lock.
*
* @author Dave Syer
*
*/
private static class BeanCallbackWrapper {
private Object bean;
private Runnable callback;
private ReadWriteLock lock;
private final String name;
private final ObjectFactory<?> objectFactory;
private final boolean proxyTargetClass;
public BeanCallbackWrapper(String name, ObjectFactory<?> objectFactory, boolean proxyTargetClass) {
this.name = name;
this.objectFactory = objectFactory;
this.proxyTargetClass = proxyTargetClass;
}
public void setCallback(Runnable callback) {
this.callback = callback;
}
public Object getBean() {
if (bean == null) {
bean = objectFactory.getObject();
if (callback != null) {
lock = new ReentrantReadWriteLock();
bean = getDisposalLockProxy(bean, lock.readLock());
}
}
return bean;
}
public void destroy() {
if (callback == null) {
return;
}
Lock lock = this.lock.writeLock();
lock.lock();
try {
callback.run();
} catch (Throwable e) {
throw wrapIfNecessary(e);
} finally {
lock.unlock();
}
bean = null;
}
/**
* Apply a lock (preferably a read lock allowing multiple concurrent access) to the bean. Callers should replace
* the bean input with the output.
*
* @param bean the bean to lock
* @param lock the lock to apply
* @return a proxy that locks while its methods are executed
*/
private Object getDisposalLockProxy(Object bean, final Lock lock) {
ProxyFactory factory = new ProxyFactory(bean);
factory.setProxyTargetClass(proxyTargetClass);
factory.addAdvice(new MethodInterceptor() {
public Object invoke(MethodInvocation invocation) throws Throwable {
lock.lock();
try {
return invocation.proceed();
} finally {
lock.unlock();
}
}
});
return factory.getProxy();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BeanCallbackWrapper other = (BeanCallbackWrapper) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
}